package com.peaksport.framework.extend.redis;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheManager.RedisCacheManagerBuilder;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import com.google.common.collect.Maps;
import com.peaksport.framework.exception.PKDevException;
import com.peaksport.framework.extend.redis.venum.ExpireTimeType;
import com.peaksport.framework.util.PKClassUtils;
import com.peaksport.framework.util.PKDevUtils;
import com.peaksport.framework.util.PKPublicUtils;

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@EnableConfigurationProperties(CacheProperties.class)
@EnableCaching
public class PKRedis2Configuration extends CachingConfigurerSupport {

	/** 是否测试环境 */
	@Value("${peak.pkms.isTest:false}")
	private boolean isTest;
	/** 公司名 */
	@Value("${peak.redis.prefix.company}")
	private String prefix_company;
	/** 系统名 */
	@Value("${peak.redis.prefix.system}")
	private String prefix_system;
	/** 缓存默认时间 */
	@Value("${peak.redis.defaultExpireTime}")
	private long defaultExpireTime;
	
	private Map<String, Long> mapExpires = Maps.newHashMap(); 
	private String cacheName;
	
	/**
	 * 初始cache
	 */
	@PostConstruct
	private void init() {
		PKDevUtils.verifyDevNull(prefix_company, "需配置redis缓存前缀项[peak.redis.prefix.company]");
		PKDevUtils.verifyDevNull(prefix_system, "需配置redis缓存前缀项[peak.redis.prefix.system]");
		if (defaultExpireTime == 0) throw PKDevException.throwException("需配置redis缓存默认时间(秒)[peak.redis.defaultExpireTime]");
		cacheName = (isTest ? "Test:" : "") + prefix_company + ":" + prefix_system + ":Cache:";
		mapExpires.clear();
		ExpireTimeType.ExpireTimeEnum[] allExpireTimeEnum = ExpireTimeType.ExpireTimeEnum.values();
		for (ExpireTimeType.ExpireTimeEnum expireTimeEnum : allExpireTimeEnum) {
			String newCacheName = expireTimeEnum.getDisplayValue();
			long expireTime = expireTimeEnum.getValue();
			mapExpires.put(newCacheName, expireTime);
		}
	}
	
//	@Bean(name = "redisTemplate")
//	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//		RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
//		redisTemplate.setConnectionFactory(redisConnectionFactory);
//
//		RedisSerializer<String> stringSerializer = new StringRedisSerializer();
//		GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
//		redisTemplate.setKeySerializer(stringSerializer);
//		redisTemplate.setHashKeySerializer(stringSerializer);
//		redisTemplate.setValueSerializer(jsonSerializer);
//		redisTemplate.setHashValueSerializer(jsonSerializer);
//
//		redisTemplate.setConnectionFactory(redisConnectionFactory);
//		return redisTemplate;
//	}

	@Bean(name = "defaultCacheManger")
	@Primary
	public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
		Map<String,RedisCacheConfiguration> cacheConfigs = new HashMap<String, RedisCacheConfiguration>();
		//redisCacheConfiguration配置模板
		RedisCacheConfiguration redisCacheConfigTpl = RedisCacheConfiguration.defaultCacheConfig().disableKeyPrefix()
				.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
		for (Map.Entry<String, Long> expire : mapExpires.entrySet()) {
			String expireName = expire.getKey();
			long expireTime = expire.getValue();
			//PS: RedisCacheConfiguration.entryTtl实际是根据模板的数据参数new一个新的RedisCacheConfiguration对象
			RedisCacheConfiguration redisCacheConfiguration = redisCacheConfigTpl.entryTtl(Duration.ofSeconds(expireTime));
			cacheConfigs.put(expireName, redisCacheConfiguration);
		}
		
		RedisCacheConfiguration defaultRedisCacheConfiguration = redisCacheConfigTpl.entryTtl(Duration.ofSeconds(defaultExpireTime));
		RedisCacheManagerBuilder redidCacheManagerBuilder = RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory));		
		redidCacheManagerBuilder.withInitialCacheConfigurations(cacheConfigs);
		redidCacheManagerBuilder.cacheDefaults(defaultRedisCacheConfiguration);
        return redidCacheManagerBuilder.build();
	}

	@Bean
	@Override
	public KeyGenerator keyGenerator() {
		return new KeyGenerator() {
			@Override
			public Object generate(Object target, Method method, Object... params) {
				StringBuffer sb = new StringBuffer();
				String expireTimeType = PKClassUtils.getCacheExpireTimeType(method);
				sb.append(cacheName);
				if (!PKPublicUtils.isEmpty(expireTimeType))
					sb.append(expireTimeType).append(PKPublicUtils.COLON);
				sb.append(target.getClass().getName()).append(".").append(method.getName());
				for (int i = 0; params != null && i < params.length; i++) {
					if (i == 0) sb.append("[");
					sb.append(params[i]);
					if (i != params.length - 1) sb.append(",");
					else sb.append("]");
				}
				return sb.toString();
			}
		};
	}
	
	
}
